package org.momo.rpc.proxy;


import org.momo.rpc.config.RpcConfigLoader;
import org.momo.rpc.constant.Constant;
import org.momo.rpc.constant.LoadBalanceKey;
import org.momo.rpc.constant.SerializeKey;
import org.momo.rpc.extention.ExtensionLoader;
import org.momo.rpc.loadbalance.LoadBalanceProvider;
import org.momo.rpc.protocol.RpcMessage;
import org.momo.rpc.protocol.RpcRequest;
import org.momo.rpc.protocol.RpcResponse;
import org.momo.rpc.protocol.constant.MessageType;
import org.momo.rpc.bootstrap.model.ReferenceMetaInfo;
import org.momo.rpc.registry.ConsumerRegistry;
import org.momo.rpc.retry.RetryPolicy;
import org.momo.rpc.util.IdGenerator;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;


public class ServiceProxyFactory {

    private final static RetryPolicy retryPolicy = ExtensionLoader.getExtensionLoader(RetryPolicy.class).getExtension(RpcConfigLoader.getRpcConfig().getRetryPolicy());

    private static final ConsumerRegistry consumerRegistry = ExtensionLoader.getExtensionLoader(ConsumerRegistry.class).getExtension(RpcConfigLoader.getRpcConfig().getRegistry());


    public static <T> T getProxy(ReferenceMetaInfo referenceMetaInfo) {
        Class<?> referenceClass = referenceMetaInfo.getReferenceClass();

        // 生成对应的loadbalancer
        for(Map.Entry<String, LoadBalanceKey> entry : referenceMetaInfo.getLoadBalanceMap().entrySet()) {
            String loadBalanceName = String.join(Constant.LOADBALANCE_DELIMITER, referenceMetaInfo.getServiceName(), referenceMetaInfo.getServiceVersion(), entry.getKey());
            LoadBalanceProvider.initLoadBalancer(loadBalanceName, entry.getValue());
        }

        Object proxy = Proxy.newProxyInstance(
                referenceClass.getClassLoader(),
                new Class<?>[]{referenceClass},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
                        // build request
                        RpcMessage.Header reqHeader = new RpcMessage.Header();
                        reqHeader.setId(IdGenerator.getId());
                        reqHeader.setMessageType(MessageType.REQUEST.getCode());
                        String serializerKey = RpcConfigLoader.getRpcConfig().getSerializer();
                        reqHeader.setSerializer(SerializeKey.getSerializerCodeByKey(serializerKey));
                        RpcRequest request = RpcRequest.builder()
                                .serviceName(referenceMetaInfo.getServiceName())
                                .serviceVersion(referenceMetaInfo.getServiceVersion())
                                .serviceKey(String.join(Constant.SERVICE_DELIMITER, referenceMetaInfo.getServiceName(), referenceMetaInfo.getServiceVersion()))
                                .methodName(method.getName())
                                .paramsClazz(method.getParameterTypes())
                                .params(args)
                                .build();
                        RpcMessage<RpcRequest> reqMessage = new RpcMessage<>(reqHeader, request);

                        // build send task
                        Class<?> methodReturnType = method.getReturnType();
                        SendRequestTask sendRequestTask = new SendRequestTask(reqMessage, methodReturnType);

                        // send request
                        RpcMessage<RpcResponse> resMessage;
                        if(consumerRegistry.canRetry(request.getServiceName(), request.getServiceVersion(), request.getMethodName())) {
                            resMessage = retryPolicy.doRetry(sendRequestTask);
                        } else {
                            resMessage = sendRequestTask.call();
                        }

                        // cast result
                        Object result = resMessage.getData().getData();
                        if(methodReturnType.isPrimitive()) {
                            return result;
                        }
                        return methodReturnType.cast(result);
                    }
                });

        return (T) proxy;
    }

}
